/*
 * CITS2230 Operating Systems - Programming Project
 * Author:           Guilherme R. Lampert
 * Student number:   21203005
 */

#include "RoundRobinScheduler.hpp"
#include <queue>

RoundRobinScheduler::RoundRobinScheduler(
		int memDumpTimeStep,
		const std::string & procListFile,
		const std::string & outputFile,
		int timeQuantum)
		: Scheduler(memDumpTimeStep, procListFile, outputFile), timeQuantum(timeQuantum)
{
	DLog("Scheduler is of type Round Robin (RR) with time quantum of " << timeQuantum);
}

RoundRobinScheduler::~RoundRobinScheduler()
{
	DLog("Destroying RR scheduler...");
}

void RoundRobinScheduler::RunWithMemorySimulation()
{
	DLog("Running process simulation with memory simulation enabled");

	const size_t numProcesses = processQueue.size();
	std::ostringstream memDumpStringStream;

	// Jobs arriving for execution
	std::queue<Process *> newArrivalsQueue;

	// Jobs blocked
	std::queue<Process *> blockedQueue;

	if (numProcesses != 0)
	{
		for (size_t i = 0; i < numProcesses; ++i)
		{
			newArrivalsQueue.push(&processQueue[i]);
		}

		// Set the clock to the start time of the first job
		simulationClock = newArrivalsQueue.front()->ArrivalTime();

		while (!newArrivalsQueue.empty() || !blockedQueue.empty())
		{
			Process * p;

			if (!newArrivalsQueue.empty() && (simulationClock >= newArrivalsQueue.front()->ArrivalTime()))
			{
				// New process arrived in the system
				p = newArrivalsQueue.front();
				newArrivalsQueue.pop();
				memoryManager.LoadProcess(p);
			}
			else
			{
				  if (blockedQueue.empty())
				  {
					    // No one to execute right now, just spin...
					    simulationClock++;
					    continue;
				  }

				// No new guys to deal with, lets run one
				// of the blocked ones
				p = blockedQueue.front();
				blockedQueue.pop();
			}

			memoryManager.ResumeProcess(p);

			int startTime = simulationClock;
			int timeConsumed = 0;
			bool isFinished = false;

			while (timeConsumed < timeQuantum)
			{
				std::string codeLine;
				int oldClock = simulationClock;
				MemoryManager::MemoryAccessResult result = memoryManager.NextCodeLine(simulationClock, codeLine);
				stepsElapsedSinceLastMemDump += simulationClock - oldClock;
				timeConsumed += simulationClock - oldClock;

				if (stepsElapsedSinceLastMemDump >= memDumpTimeStep)
				{
					memoryManager.PrintMemoryDump(memDumpStringStream, simulationClock);
					stepsElapsedSinceLastMemDump = 0;
				}

				if (result == MemoryManager::NoMoreCode)
				{
					// Process has finished execution
					memoryManager.UnloadProcess(p);
					isFinished = true;
					break;
				}
				else if (result == MemoryManager::PageFault)
				{
					// Fetching the line caused a page fault,
					// process must be blocked
					p->ExecuteCodeLine(codeLine);
					break;
				}
				else
				{
					p->ExecuteCodeLine(codeLine);
				}
			}

			int stopTime = simulationClock;

			if (!isFinished)
			{
				p->AddRRTimes(startTime, stopTime);

				// Job was blocked because its time quantum ended
				blockedQueue.push(p);
			}
		}

		Output() << "RR Process Scheduling Times:" << std::endl;
		for (size_t j = 0; j < numProcesses; ++j)
		{
			Process * p = &processQueue[j];
			Output() << p->Name().c_str() << " ";
			for (size_t k = 0; k < p->SavedRRTimes().size(); ++k)
			{
				Output() << std::setfill(' ') << std::setw(2) << p->SavedRRTimes()[k].startTime << " ";
			}
			Output() << std::endl;
		}
	}

	Output() << std::endl;
	Output() << "Memory Simulation Dump:" << std::endl;
	Output() << memDumpStringStream.str().c_str();

	DLog("Simulation finished");
	DLog("Simulated " << numProcesses << " processes");
}

void RoundRobinScheduler::RunWithoutMemorySimulation()
{
	DLog("Running process simulation without memory simulation");

	const size_t numProcesses = processQueue.size();

	// Jobs arriving for execution
	std::queue<Process *> newArrivalsQueue;

	// Jobs blocked
	std::queue<Process *> blockedQueue;

	if (numProcesses != 0)
	{
		for (size_t i = 0; i < numProcesses; ++i)
		{
			newArrivalsQueue.push(&processQueue[i]);
		}

		// Set the clock to the start time of the first job
		simulationClock = newArrivalsQueue.front()->ArrivalTime();

		while (!newArrivalsQueue.empty() || !blockedQueue.empty())
		{
			Process * p;

			if (!newArrivalsQueue.empty() && (simulationClock >= newArrivalsQueue.front()->ArrivalTime()))
			{
				// New process arrived in the system
				p = newArrivalsQueue.front();
				newArrivalsQueue.pop();
			}
			else
			{
				// No new guys to deal with, lets run one
				// of the blocked ones
				p = blockedQueue.front();
				blockedQueue.pop();
			}

			int startTime = simulationClock;
			int timeConsumed = 0;
			bool isFinished = false;

			while (timeConsumed < timeQuantum)
			{
				std::string codeLine;
				const int nextCodeLine = p->NextCodeLine();

				if (nextCodeLine < (int)p->CodeLines().size())
				{
					codeLine = p->CodeLines()[nextCodeLine];
					p->ExecuteCodeLine(codeLine);
					simulationClock++;
					timeConsumed++;
				}
				else
				{
					// Process has finished execution
					isFinished = true;
					break;
				}
			}

			int stopTime = simulationClock;

			if (!isFinished)
			{
				p->AddRRTimes(startTime, stopTime);

				// Job was blocked because its time quantum ended
				blockedQueue.push(p);
			}
		}

		Output() << "RR Process Scheduling Times:" << std::endl;
		for (size_t j = 0; j < numProcesses; ++j)
		{
			Process * p = &processQueue[j];
			Output() << p->Name().c_str() << " ";
			for (size_t k = 0; k < p->SavedRRTimes().size(); ++k)
			{
				Output() << std::setfill(' ') << std::setw(2) << p->SavedRRTimes()[k].startTime << " ";
			}
			Output() << std::endl;
		}
	}

	DLog("Simulation finished");
	DLog("Simulated " << numProcesses << " processes");
}
